package fr.upmc.aladyn.transactionables.javassist.injection;

import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.NotFoundException;
import javassist.Translator;
import javassist.expr.Handler;
import javassist.expr.ExprEditor;

/**
 * <p>Javassist translator used to inject code to <code>Transactionnable</code> methods and Setters.</p>
 * @author Alexis Deluze
 */
public class TransactionableTranslator implements Translator {
	
	private static final String _INIT = "{ fr.upmc.aladyn.transactionables.saveobject.SaveManager.getManager().newStackBlock(); }";
	private static final String _UNINIT = "{ fr.upmc.aladyn.transactionables.saveobject.SaveManager.getManager().pop(); }";
	private static final String _SAVE = "{ fr.upmc.aladyn.transactionables.saveobject.SaveManager.getManager().save(this); }";
	private static final String _RESTORE = "{ fr.upmc.aladyn.transactionables.saveobject.SaveManager.getManager().restore(); }";
	private static final String _RESTORE_UNINIT_THROW = "{ fr.upmc.aladyn.transactionables.saveobject.SaveManager.getManager().restore(); fr.upmc.aladyn.transactionables.saveobject.SaveManager.getManager().pop(); throw $e;}";	

	/**
	 * <p>Inject the save and restoration code for every @Transactionnable method found. Inject the save code for every Setter found.</p>
	 * @param pool The class pool
	 * @param classname The name of the loaded class
	 * @throws NotFoundException
	 * @throws CannotCompileException
	 * @see javassist.Translator#onLoad(javassist.ClassPool, java.lang.String)
	 */
	@Override
	public void onLoad(ClassPool pool, String classname) throws NotFoundException, CannotCompileException {
		// recuperation de la classe a modifier
		CtClass cc = pool.get(classname);
		System.out.println("[JAVASSIST] Chargement de la classe "+cc.getSimpleName());
		
		// parcours des methodes
		for(CtMethod cm : cc.getDeclaredMethods()){

			if(cm.getName().startsWith("set")){
				System.out.println("\t- La methode "+cm.getName()+" est un setter, on injecte le code de sauvegarde");
				cm.insertBefore(_SAVE);
			}	
			// si la methode est transactionnable
			if(cm.hasAnnotation(fr.upmc.aladyn.transactionables.annotations.Transactionable.class)){
				
				System.out.println("\t- La methode "+cm.getName()+" est Transactionnable");
				
				// ajout du code de restauration dans chaque clause Catch existante
				System.out.println("\t\t- Injection du code de restauration dans les Catch existants");
				cm.instrument(
					new ExprEditor() {
						public void edit(Handler h) throws CannotCompileException {
							System.out.println("\t\t\t- Ligne numero : "+h.getLineNumber());
							h.insertBefore(_RESTORE);
						}
					}
				);
				
				// on insert notre code d'initialisation de la sauvegarde
				System.out.println("\t\t- Injection du code d'initialisation de la sauvegarde au debut de la methode");
				cm.insertBefore(_INIT);
				
				// supression du bloc de sauvegarde dans la pile
				System.out.println("\t\t- Injection du code de supression de la sauvegarde ala fin de la methode");
				cm.insertAfter(_UNINIT);
				
				// on ajoute un try catch englobant pour la restauration
				System.out.println("\t\t- Ajout d'un Catch et injection du code de restauration + Throw de l'exception");
				cm.addCatch(_RESTORE_UNINIT_THROW, pool.get("java.lang.Exception"));
			}
		}
	}
	
	
	/**
	 * <p>Method called at the initialization of the TransactionnableTranslator. Does nothing.</p>
	 * @param pool the class pool
	 * @throws NotFoundException
	 * @throws CannotCompileException
	 * @see javassist.Translator#start(javassist.ClassPool)
	 */
	@Override
	public void start(ClassPool pool) throws NotFoundException, CannotCompileException {}
}